#include "can_device.h"

#include <QThread>
#define DF_PLUGIN_NAME      "socketcan"
#define DF_DEV_INTERFACE    "can0"

static CanDevice *g_pCanDev = nullptr;
static QList<QPair<int, QByteArray>> g_lstSndBuf;

/*
 * Function:    CanDevice
 * Description: The default constructor
 * Paramer:     void
 * Return:
 */
CanDevice::CanDevice(QObject *parent) : QObject(parent)
{
    qDebug() << Q_FUNC_INFO;
}

/*
 * Function:    ~CanDevice
 * Description: The default destructor
 * Paramer:     void
 * Return:
 */
CanDevice::~CanDevice()
{
    qDebug() << Q_FUNC_INFO;

    if(m_pWriteDataTimer) {
        if(m_pWriteDataTimer->isActive()) {
            m_pWriteDataTimer->stop();
            m_pWriteDataTimer = nullptr;
        }
    }
}

/*
 * Function:    getInstance
 * Description: Get an instance of "CanDevice" class, if exist, return it
 * Paramer:     QObject *pParent - the pointer of QObject class
 * Return:      the pointer of "CanDevice" class
 */
CanDevice* CanDevice::getInstance(QObject *pParent)
{
    qDebug() << Q_FUNC_INFO;

    if(!g_pCanDev) {
        // Create a new object if it doesn't exist
        g_pCanDev = new CanDevice(pParent);
        if(!g_pCanDev) return nullptr;

        // After constructing object, initialize the object
        g_pCanDev->_initialze();
    }

    return g_pCanDev;
}

/*
 * Function:    _initialze
 * Description: After constructing object, initialize the object
 * Paramer:     void
 * Return:      void
 */
void CanDevice::_initialze()
{
    qDebug() << Q_FUNC_INFO;

    m_stCanSetting.sPluginName = DF_PLUGIN_NAME;
    m_stCanSetting.sDevInterface = DF_DEV_INTERFACE;
    m_stCanSetting.lstCanConfig.clear();

    // loopback
    m_stCanSetting.lstCanConfig.append(parBusConfigItem(QCanBusDevice::LoopbackKey, QVariant(false)));
    // receive own
    m_stCanSetting.lstCanConfig.append(parBusConfigItem(QCanBusDevice::ReceiveOwnKey, QVariant(false)));
    // bitrate
    m_stCanSetting.lstCanConfig.append(parBusConfigItem(QCanBusDevice::BitRateKey, QVariant(DEF_BITRATE)));
    // CAN FD
    m_stCanSetting.lstCanConfig.append(parBusConfigItem(QCanBusDevice::CanFdKey, QVariant(false)));
    // data bitrate
    m_stCanSetting.lstCanConfig.append(parBusConfigItem(QCanBusDevice::DataBitRateKey, QVariant(DEF_BITRATE)));

    QString sErrInfo = "";
    m_pCanDevice = QCanBus::instance()->createDevice(m_stCanSetting.sPluginName, m_stCanSetting.sDevInterface, &sErrInfo);
    if (!m_pCanDevice) {
        qDebug() << Q_FUNC_INFO << QString("Error creating device '%1', reason: '%2'").arg(m_stCanSetting.sPluginName).arg(sErrInfo);
    }
    else {
        // Signal-slot initialization
        std::vector<bool> vecConnect;
        vecConnect.push_back(QObject::connect(m_pCanDevice, &QCanBusDevice::errorOccurred, this, &CanDevice::slotErrorOccurred, static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
        vecConnect.push_back(QObject::connect(m_pCanDevice, &QCanBusDevice::framesReceived, this, &CanDevice::slotFramesReceived, static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
        vecConnect.push_back(QObject::connect(m_pCanDevice, &QCanBusDevice::framesWritten, this, &CanDevice::slotFramesWritten, static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
        vecConnect.push_back(QObject::connect(m_pCanDevice, &QCanBusDevice::stateChanged, this, &CanDevice::slotStateChanged, static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
        for(int i=0; i<(int)vecConnect.size(); i++) {
          Q_ASSERT(vecConnect.at(i));
        }
    }
}

/*
 * Function:    openCAN
 * Description: open a CAN bus
 * Paramer:     void
 * Return:      int, 0: success; otherwise: error code
 */
int CanDevice::openCAN()
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    // create an timer
    if(!m_pWriteDataTimer) m_pWriteDataTimer = new QTimer();
    Q_ASSERT(m_pWriteDataTimer);

    if (!m_pCanDevice) {
        nRetVal = -1;
    }
    else {
        // Configuration CAN
//        foreach (const parBusConfigItem &item, m_stCanSetting.lstCanConfig) {
//            m_pCanDevice->setConfigurationParameter(item.first, item.second);
//        }

        // Open the CAN device
        if(!m_pCanDevice->connectDevice()) {
            qDebug() << Q_FUNC_INFO << QString("Connection error: %1").arg(m_stCanSetting.sPluginName).arg(m_pCanDevice->errorString());

            nRetVal = -2;
        }

        // Signal-slot initialization
        std::vector<bool> vecConnect;
        vecConnect.push_back(QObject::connect(m_pWriteDataTimer, SIGNAL(timeout()), this, SLOT(slotSendDataTimer())));
        for(int i=0; i<(int)vecConnect.size(); i++) {
          Q_ASSERT(vecConnect.at(i));
        }
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    closeCAN
 * Description: close a CAN bus
 * Paramer:     void
 * Return:      int, 0: success; otherwise: error code
 */
void CanDevice::closeCAN()
{
    qDebug() << Q_FUNC_INFO;

    // Close the CAN device
    m_pCanDevice->disconnectDevice();

    if(m_pWriteDataTimer) QObject::disconnect(m_pWriteDataTimer, SIGNAL(timeout()), this, SLOT(slotSendDataTimer()));

    // stop timer
    if(m_pWriteDataTimer) {
        if(m_pWriteDataTimer->isActive()) {
            m_pWriteDataTimer->stop();
            m_pWriteDataTimer = nullptr;
        }
    }
}

/*
 * Function:    sendFrameData
 * Description: send an frame data to CAN bus
 * Paramer:     int nFrameId - frame ID
 *              const QByteArray &baContent - frame data
 * Return:      the size of written
 */
int CanDevice::sendFrameData(int nFrameId, const QByteArray &baFrameData, bool bQueued)
{
    qDebug() << Q_FUNC_INFO << nFrameId << baFrameData.size() << bQueued;

    int nRetVal = 0;

    if(!bQueued) {
        nRetVal = _sendFrameData(nFrameId, baFrameData);
    }
    else {
        QPair<int, QByteArray> parFrameData(nFrameId, baFrameData);

        g_lstSndBuf << parFrameData;

        m_pWriteDataTimer->start(0.1 * 1000);
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    slotErrorOccurred
 * Description: An CAN bus error occurs
 * Paramer:     QCanBusDevice::CanBusError error - bus error
 * Return:      void
 */
void CanDevice::slotErrorOccurred(QCanBusDevice::CanBusError error) const
{
    qDebug() << Q_FUNC_INFO << error << m_pCanDevice->errorString();

    switch (error) {
    case QCanBusDevice::ReadError:
    case QCanBusDevice::WriteError:
    case QCanBusDevice::ConnectionError:
    case QCanBusDevice::ConfigurationError:
    case QCanBusDevice::UnknownError:
        break;
    default:
        break;
    }
}

/*
 * Function:    slotFramesReceived
 * Description: One or more frames have been received by the CAN bus
 * Paramer:     void
 * Return:      void
 */
void CanDevice::slotFramesReceived()
{
    qDebug() << Q_FUNC_INFO << "---------";

    QMutexLocker mtxLocker(&m_mtxRecv);
    if (!m_pCanDevice) return;

//    QVector<QCanBusFrame> vecTotalFrameCan;

    while(m_pCanDevice->framesAvailable()) {
        if(0 == 1) {
            // Get frames from the frame queue
            QVector<QCanBusFrame> vecFrameCan = m_pCanDevice->readAllFrames();
            foreach (QCanBusFrame frameCan, vecFrameCan) {
                // Retrieve frame data
                if (QCanBusFrame::ErrorFrame == frameCan.frameType()) {
                    QString sFrame = m_pCanDevice->interpretErrorFrame(frameCan);

                    qDebug() << Q_FUNC_INFO << "ErrorFrame" << frameCan.frameId() << frameCan.frameType() << sFrame;
                }
                else {
                    QString sFrame = frameCan.toString();

                    if(0 < frameCan.payload().size()) {
                        // Notify upper level business and receive new data
                        emit signalFramesReceived(frameCan.frameId(), frameCan.payload());

                        qDebug() << Q_FUNC_INFO << frameCan.frameId() << frameCan.frameType() << sFrame;
                    }
                    else {
                        qDebug() << Q_FUNC_INFO << frameCan.frameId() << frameCan.frameType() << "a null frame";
                    }
                }
            }
        }

        if(1 == 1) {
            const QCanBusFrame frameCan = m_pCanDevice->readFrame();

            // Retrieve frame data
            if (QCanBusFrame::ErrorFrame == frameCan.frameType()) {
                QString sFrame = m_pCanDevice->interpretErrorFrame(frameCan);

                qDebug() << Q_FUNC_INFO << "ErrorFrame" << frameCan.frameId() << frameCan.frameType() << sFrame;
            }
            else {
                QString sFrame = frameCan.toString();

                if(0 < frameCan.payload().size()) {
                    // Notify upper level business and receive new data
                    emit signalFramesReceived(frameCan.frameId(), frameCan.payload());

                    qDebug() << Q_FUNC_INFO << frameCan.frameId() << frameCan.frameType() << sFrame;
                }
                else {
                    qDebug() << Q_FUNC_INFO << frameCan.frameId() << frameCan.frameType() << "a null frame";
                }
            }
        }
    }

//    foreach (QCanBusFrame frameCan, vecTotalFrameCan) {
//        // Notify upper level business and receive new data
//        emit signalFramesReceived(frameCan.frameId(), frameCan.payload());
//    }
}

/*
 * Function:    slotFramesWritten
 * Description: A payload of frames has been written to the CAN bus
 * Paramer:     qint64 nDataLen - the size of data written
 * Return:      void
 */
void CanDevice::slotFramesWritten(qint64 nFramesCount)
{
    qDebug() << Q_FUNC_INFO << nFramesCount;
}

/*
 * Function:    slotStateChanged
 * Description: State changed on the CAN bus
 * Paramer:     QCanBusDevice::CanBusDeviceState stateCan - CAN status
 * Return:      void
 */
void CanDevice::slotStateChanged(QCanBusDevice::CanBusDeviceState stateCan)
{
    qDebug() << Q_FUNC_INFO << stateCan;
}

/*
 * Function:    slotSendDataTimer
 * Description: write data to COM
 * Paramer:     void
 * Return:      void
 */
void CanDevice::slotSendDataTimer()
{
    qDebug() << Q_FUNC_INFO;

    // stop the timer firstly
    m_pWriteDataTimer->stop();

    // Send the command handler
    this->_commandHandler();
}

/*
 * Function:    _commandHandler
 * Description: the command handler
 * Paramer:     void
 * Return:      void
 */
void CanDevice::_commandHandler()
{
    qDebug() << Q_FUNC_INFO << g_lstSndBuf.size();

    QPair<int, QByteArray> mapFrameData = g_lstSndBuf.takeFirst();
    int nFrameId = mapFrameData.first;
    QByteArray baFrameData = mapFrameData.second;

    if(0 < baFrameData.size()) {
        _sendFrameData(nFrameId, baFrameData);
    }
}

/*
 * Function:    _commandHandler
 * Description: the command handler
 * Paramer:     void
 * Return:      void
 */
int CanDevice::_sendFrameData(int nFrameId, const QByteArray &baFrameData)
{
    QMutexLocker mtxLocker(&m_mtxSend);
    int nRetVal = 0;

    int nDataSize = baFrameData.size();
    int nDataLen = nDataSize / 8;
    int nDataLenEx = nDataSize % 8;

    if (!m_pCanDevice) {
        nRetVal = -1;
        goto do_exit;
    }
    else if(0 >= baFrameData.size()) {
        nRetVal = -2;
        goto do_exit;
    }
    else {
        // Get the actual data length
        if(0 < nDataLenEx)
            nDataSize = nDataLen+1;
        else
            nDataSize = nDataLen;

        // Send the entire set of frame data
        for(int i=0; i<nDataLen; i++) {
            QByteArray baPayload;
            for(int j=0; j<8; j++) {
                baPayload.append(baFrameData.at(i*8 + j));
            }
            qDebug() << Q_FUNC_INFO << baPayload.size() << baPayload.toHex(' ').toUpper();

            QCanBusFrame frameCan = QCanBusFrame(nFrameId, baPayload);
            frameCan.setExtendedFrameFormat(false);
            frameCan.setFlexibleDataRateFormat(false);
            frameCan.setBitrateSwitch(false);

            // Write a frame into serialbus
            if(!m_pCanDevice->writeFrame(frameCan)) {
                nRetVal = -3;
                goto do_exit;
            }
        }

        // Send remaining frame data
        if(0 < nDataLenEx) {
            QByteArray payload;
            for(int j=0; j<nDataLenEx; j++) {
                payload.append(baFrameData.at(nDataLen * 8 + j));
            }
            qDebug() << Q_FUNC_INFO << payload.size() << payload.toHex(' ').toUpper();

            QCanBusFrame frameCan = QCanBusFrame(nFrameId, payload);
            frameCan.setExtendedFrameFormat(false);
            frameCan.setFlexibleDataRateFormat(false);
            frameCan.setBitrateSwitch(false);

            // Write a frame into serialbus
            if(!m_pCanDevice->writeFrame(frameCan)) {
                nRetVal = -4;
                goto do_exit;
            }
        }

        nRetVal = baFrameData.size();
    }

do_exit:
    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}
